/*
 * Copyright 2013 ENERKO Informatik GmbH
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * THIS SOFTWARE IS  PROVIDED BY THE  COPYRIGHT HOLDERS AND  CONTRIBUTORS "AS IS"
 * AND ANY  EXPRESS OR  IMPLIED WARRANTIES,  INCLUDING, BUT  NOT LIMITED  TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL  THE COPYRIGHT HOLDER OR CONTRIBUTORS  BE LIABLE
 * FOR ANY  DIRECT, INDIRECT,  INCIDENTAL, SPECIAL,  EXEMPLARY, OR  CONSEQUENTIAL
 * DAMAGES (INCLUDING,  BUT NOT  LIMITED TO,  PROCUREMENT OF  SUBSTITUTE GOODS OR
 * SERVICES; LOSS  OF USE,  DATA, OR  PROFITS; OR  BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT  LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE  USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package com.fw.ui.core;

import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.context.ApplicationContext;
import org.springframework.security.core.Authentication;
import org.springframework.web.context.support.WebApplicationContextUtils;

import com.vaadin.navigator.View;
import com.vaadin.navigator.ViewProvider;
import com.vaadin.server.VaadinServlet;

/**
 * <p>
 * This is a specialized ViewProvider that takes care of evaluating {@link ViewDescription} especially
 * {@link ViewDescription#requiredPermissions()}. Those expressions are evaluated within the current Spring Security Context. If they
 * evaluate to false, the view in concern is not even instantiated.
 * </p>
 * <p>
 * This ViewProvider must be retrieved through a factory method as the application context will be transiently stored and thus cannot be
 * autowired through constructor injection.
 * </p>
 * <p>
 * This also requires a the spring-instrumentation agent. See pom.xml or read more about Configurable objects here <a href=
 * "http://info.michael-simons.eu/2013/03/12/vaadin-spring-using-configurable-in-vaadin-components/" > Vaadin & Spring: Using @Configurable
 * in Vaadin Components </a>
 * </p>
 * 
 * @author Michael J. Simons, 2013-03-04
 */
@Configurable
public class SpringViewProvider implements ViewProvider {

    private static final long serialVersionUID = -8555986824827085073L;
    /** Will be injected through AspectJ upon new and deserialization */

    transient ApplicationContext applicationContext = WebApplicationContextUtils.getWebApplicationContext(VaadinServlet.getCurrent()
	    .getServletContext());

    /** The viewname -> view class mapping */
    final Map<String, String> mapNamePathViews = new HashMap<String, String>();
    final Map<String, Class<? extends View>> views = new HashMap<String, Class<? extends View>>();
    final Map<String, Class<? extends View>> mapViews = new HashMap<String, Class<? extends View>>();
    final Map<String, Class<? extends View>> allViews = new HashMap<String, Class<? extends View>>();

    /** Cached instances of views */
    final Map<String, View> cachedInstances = new HashMap<String, View>();
    private Boolean enableCaching;

    public final static ViewProvider createViewProvider(final Authentication authentication) {
	return createViewProvider(authentication, null);
    }

    @SuppressWarnings("unchecked")
    public final static ViewProvider createViewProvider(final Authentication authentication, Boolean enableCaching) {
	final SpringViewProvider springViewProvider = new SpringViewProvider();
	springViewProvider.enableCaching = enableCaching;
	try {
	    final ApplicationContext applicationContext = springViewProvider.applicationContext;
	    for (String beanName : applicationContext.getBeanDefinitionNames()) {
		final Class<?> beanClass = applicationContext.getType(beanName);
		if (beanClass.isAnnotationPresent(ViewDescription.class) && View.class.isAssignableFrom(beanClass)) {
		    final ViewDescription viewDescription = beanClass.getAnnotation(ViewDescription.class);
		    // String viewRoleKey = "";// TODO PermissionUtils.VIEW + viewDescription.name() + PermissionUtils.VIEW_ROLE;
		    // String roles = "";// TODO PermissionUtils.getVarible(viewRoleKey);
		    // if (authentication.getAuthorities() != null) {
		    // boolean isAccessView = false;
		    // for (GrantedAuthority authority : authentication.getAuthorities()) {
		    // if (roles.toLowerCase().indexOf(authority.getAuthority().toLowerCase()) > -1) {
		    // isAccessView = true;
		    // break;
		    // }
		    // }
		    // if (isAccessView) {
		    // springViewProvider.mapViews.put(viewDescription.name(), (Class<? extends View>) beanClass);
		    // String navigateKeyStr = "";// TODO PermissionUtils.VIEW + viewDescription.name() + PermissionUtils.VIEW_NAME;
		    // String navigateStr = "";// TODO PermissionUtils.getVarible(navigateKeyStr);
		    // if (navigateStr != null) {
		    // springViewProvider.views.put(navigateStr, (Class<? extends View>) beanClass);
		    // }
		    // }
		    // }
		    springViewProvider.mapViews.put(viewDescription.name(), (Class<? extends View>) beanClass);
		    springViewProvider.views.put(viewDescription.path(), (Class<? extends View>) beanClass);
		    springViewProvider.allViews.put(viewDescription.name(), (Class<? extends View>) beanClass);
		    springViewProvider.mapNamePathViews.put(viewDescription.name(), viewDescription.path());
		}
	    }
	} catch (Exception e) {
	    e.printStackTrace();
	}
	return springViewProvider;
    }

    @SuppressWarnings("unchecked")
    public final static ViewProvider createViewProvider(Boolean enableCaching) {
	final SpringViewProvider springViewProvider = new SpringViewProvider();
	springViewProvider.enableCaching = enableCaching;
	try {
	    final ApplicationContext applicationContext = springViewProvider.applicationContext;
	    for (String beanName : applicationContext.getBeanDefinitionNames()) {
		final Class<?> beanClass = applicationContext.getType(beanName);
		if (beanClass.isAnnotationPresent(ViewDescription.class) && View.class.isAssignableFrom(beanClass)) {
		    final ViewDescription viewDescription = beanClass.getAnnotation(ViewDescription.class);
		    // String viewRoleKey = "";// TODO PermissionUtils.VIEW + viewDescription.name() + PermissionUtils.VIEW_ROLE;
		    // String roles = "";// TODO PermissionUtils.getVarible(viewRoleKey);
		    // if (authentication.getAuthorities() != null) {
		    // boolean isAccessView = false;
		    // for (GrantedAuthority authority : authentication.getAuthorities()) {
		    // if (roles.toLowerCase().indexOf(authority.getAuthority().toLowerCase()) > -1) {
		    // isAccessView = true;
		    // break;
		    // }
		    // }
		    // if (isAccessView) {
		    // springViewProvider.mapViews.put(viewDescription.name(), (Class<? extends View>) beanClass);
		    // String navigateKeyStr = "";// TODO PermissionUtils.VIEW + viewDescription.name() + PermissionUtils.VIEW_NAME;
		    // String navigateStr = "";// TODO PermissionUtils.getVarible(navigateKeyStr);
		    // if (navigateStr != null) {
		    // springViewProvider.views.put(navigateStr, (Class<? extends View>) beanClass);
		    // }
		    // }
		    // }
		    springViewProvider.mapViews.put(viewDescription.name(), (Class<? extends View>) beanClass);
		    springViewProvider.views.put(viewDescription.path(), (Class<? extends View>) beanClass);
		    springViewProvider.allViews.put(viewDescription.name(), (Class<? extends View>) beanClass);
		    springViewProvider.mapNamePathViews.put(viewDescription.name(), viewDescription.path());
		}
	    }
	} catch (Exception e) {
	    e.printStackTrace();
	}
	return springViewProvider;
    }

    private SpringViewProvider() {
    }

    @Override
    public String getViewName(String viewAndParameters) {
	String rv = null;
	if (viewAndParameters != null) {
	    if (views.containsKey(viewAndParameters))
		rv = viewAndParameters;
	    else {
		for (String viewName : views.keySet()) {
		    if (viewAndParameters.startsWith(viewName + "/")) {
			rv = viewName;
			break;
		    }
		}
	    }
	}
	if (rv == null) {
	    rv = "/main";
	}
	return rv;
    }

    @Override
    public View getView(String viewName) {
	View rv = null;

	Class<? extends View> clazz = this.views.get(viewName);
	if (clazz != null) {
	    if (isCachingEnabled() && clazz.getAnnotation(ViewDescription.class).cacheable()) {
		rv = this.cachedInstances.get(viewName);
		if (rv == null)
		    this.cachedInstances.put(viewName, rv = applicationContext.getBean(clazz));
	    } else {
		rv = applicationContext.getBean(clazz);
	    }
	}
	return rv;
    }

    public View getViewByAppName(String viewAppName) {
	View rv = null;

	Class<? extends View> clazz = this.mapViews.get(viewAppName);
	if (clazz != null) {
	    if (isCachingEnabled() && clazz.getAnnotation(ViewDescription.class).cacheable()) {
		rv = this.cachedInstances.get(viewAppName);
		if (rv == null)
		    this.cachedInstances.put(viewAppName, rv = applicationContext.getBean(clazz));
	    } else {
		rv = applicationContext.getBean(clazz);
	    }
	}
	return rv;
    }

    public Map<String, Class<? extends View>> getViews() {
	return this.views;
    }

    public Map<String, Class<? extends View>> getAllViews() {
	return this.allViews;
    }

    public Map<String, String> getMapNamePathViews() {
	return this.mapNamePathViews;
    }

    /**
     * Caching should be enabled only in "prod" environment
     * 
     * @return true if caching is enabled
     */
    boolean isCachingEnabled() {
	return (enableCaching != null && enableCaching)
		|| Arrays.asList(this.applicationContext.getEnvironment().getActiveProfiles()).contains("prod");
    }
}